﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Speech.Recognition;
using System.Speech.Synthesis;

namespace Defilade
{
    public class RecognitionEngine : IDisposable
    {
        public enum Subject
        {
            Player, Target
        }

        public delegate void ActionEventHandler();
        public delegate void CoordinateInputEventHandler(Subject subject, Coordinate recognizedCoordinate);

        public event ActionEventHandler RecognizedRangeRequest;
        public event CoordinateInputEventHandler RecognizedCoordinate;

        private SpeechRecognizer recognizer;
        private static Dictionary<String, int> numberStringToInt = new Dictionary<string, int>();
        private static HashSet<String> objectStrings = new HashSet<String>(new String[] { "player", "target" });
        private static HashSet<String> directionStrings = new HashSet<String>(new String[] { "north", "west" });
        private static HashSet<String> rangeCommands = new HashSet<string>(new String[] { "range" });

        static RecognitionEngine()
        {
            #region Set Up numberStringToInt Mapping
            numberStringToInt.Add("zero", 0);
            numberStringToInt.Add("one", 1);
            numberStringToInt.Add("two", 2);
            numberStringToInt.Add("three", 3);
            numberStringToInt.Add("four", 4);
            numberStringToInt.Add("five", 5);
            numberStringToInt.Add("six", 6);
            numberStringToInt.Add("seven", 7);
            numberStringToInt.Add("eight", 8);
            numberStringToInt.Add("nine", 9);
            numberStringToInt.Add("ten", 10);
            numberStringToInt.Add("eleven", 11);
            numberStringToInt.Add("twelve", 12);
            numberStringToInt.Add("thirteen", 13);
            numberStringToInt.Add("fourteen", 14);
            numberStringToInt.Add("fifteen", 15);
            numberStringToInt.Add("sixteen", 16);
            numberStringToInt.Add("seventeen", 17);
            numberStringToInt.Add("eighteen", 18);
            numberStringToInt.Add("nineteen", 19);
            numberStringToInt.Add("twenty", 20);
            numberStringToInt.Add("twenty one", 21);
            numberStringToInt.Add("twenty two", 22);
            numberStringToInt.Add("twenty three", 23);
            numberStringToInt.Add("twenty four", 24);
            numberStringToInt.Add("twenty five", 25);
            numberStringToInt.Add("twenty six", 26);
            numberStringToInt.Add("twenty seven", 27);
            numberStringToInt.Add("twenty eight", 28);
            numberStringToInt.Add("twenty nine", 29);
            numberStringToInt.Add("thirty", 30);
            numberStringToInt.Add("thirty one", 31);
            numberStringToInt.Add("thirty two", 32);
            numberStringToInt.Add("thirty three", 33);
            numberStringToInt.Add("thirty four", 34);
            numberStringToInt.Add("thirty five", 35);
            numberStringToInt.Add("thirty six", 36);
            numberStringToInt.Add("thirty seven", 37);
            numberStringToInt.Add("thirty eight", 38);
            numberStringToInt.Add("thirty nine", 39);
            numberStringToInt.Add("fourty", 40);
            numberStringToInt.Add("fourty one", 41);
            numberStringToInt.Add("fourty two", 42);
            numberStringToInt.Add("fourty three", 43);
            numberStringToInt.Add("fourty four", 44);
            numberStringToInt.Add("fourty five", 45);
            numberStringToInt.Add("fourty six", 46);
            numberStringToInt.Add("fourty seven", 47);
            numberStringToInt.Add("fourty eight", 48);
            numberStringToInt.Add("fourty nine", 49);
            numberStringToInt.Add("fifty", 50);
            numberStringToInt.Add("fifty one", 51);
            numberStringToInt.Add("fifty two", 52);
            numberStringToInt.Add("fifty three", 53);
            numberStringToInt.Add("fifty four", 54);
            numberStringToInt.Add("fifty five", 55);
            numberStringToInt.Add("fifty six", 56);
            numberStringToInt.Add("fifty seven", 57);
            numberStringToInt.Add("fifty eight", 58);
            numberStringToInt.Add("fifty nine", 59);
            numberStringToInt.Add("sixty", 60);
            #endregion
        }

        public RecognitionEngine()
        {
            Choices numberChoices = new Choices(numberStringToInt.Keys.ToArray<String>());
            Choices smallNumberChoices = new Choices(numberStringToInt.Keys.Take<String>(60).ToArray<String>());
            Choices rangeChoices = new Choices(rangeCommands.ToArray<String>());

            //Build a grammar that allows the user to say things like:
            //player [at] 32.45.23 [degrees] North
            GrammarBuilder coordinateGrammar = new GrammarBuilder();
            coordinateGrammar.Append(new Choices(objectStrings.ToArray<String>()));
            coordinateGrammar.AppendWildcard();
            coordinateGrammar.Append(numberChoices);
            coordinateGrammar.Append(new Choices("degrees", "degree", "point"));
            coordinateGrammar.Append(smallNumberChoices);
            coordinateGrammar.Append(new Choices("minutes", "minute", "point"));
            coordinateGrammar.Append(smallNumberChoices);
            coordinateGrammar.AppendWildcard();
            coordinateGrammar.Append(new Choices(directionStrings.ToArray<String>()));

            //Build a grammar that allows the user to request the current range
            GrammarBuilder rangeGrammar = new GrammarBuilder(rangeChoices);

            //Build the main grammar, that allows the user to enter either of the above commands
            GrammarBuilder commandGrammar = new GrammarBuilder(new Choices(rangeGrammar, coordinateGrammar));
            Grammar g = new Grammar(commandGrammar);
            g.Name = "Coorinate Grammar";
            g.Enabled = true;

            recognizer = new SpeechRecognizer();

            System.Console.WriteLine("Starting...");
            System.Console.WriteLine("Recognizer: " + recognizer.RecognizerInfo.Name);
            foreach (String key in recognizer.RecognizerInfo.AdditionalInfo.Keys)
            {
                System.Console.WriteLine("\t" + key + ": " + recognizer.RecognizerInfo.AdditionalInfo[key]);
            }

            recognizer.LoadGrammar(g);
            recognizer.Enabled = true;
            recognizer.PauseRecognizerOnRecognition = true;
#if DEBUG
            recognizer.SpeechHypothesized += new EventHandler<SpeechHypothesizedEventArgs>(recognizer_SpeechHypothesized);
#endif
            recognizer.SpeechRecognized += new EventHandler<SpeechRecognizedEventArgs>(recognizer_SpeechRecognized);
        }

        void recognizer_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            System.Console.WriteLine("RECOGNIZED: " + e.Result.Text);

            if (rangeCommands.Contains(e.Result.Words[0].Text))
            {
                if (RecognizedRangeRequest != null)
                {
                    RecognizedRangeRequest();
                }
            }
            else
            {
                //Setup some variables to hold results
                String subjectString = null;
                int degrees = -1;
                int minutes = -1;
                int seconds = -1;
                String directionString = null;

                //Make this a little easier to work with
                //we will be destorying the list during processing
                Queue<String> recognizedWords = new Queue<String>();
                foreach (RecognizedWordUnit wordUnit in e.Result.Words)
                {
                    recognizedWords.Enqueue(wordUnit.Text);
                }

                //Retrieve the values from the Words array
                subjectString = recognizedWords.Dequeue();
                if (recognizedWords.Peek() == "...") { recognizedWords.Dequeue(); } //Possible wildcard

                degrees = ExtractNumber(recognizedWords);
                recognizedWords.Dequeue(); //Point
                minutes = ExtractNumber(recognizedWords);
                recognizedWords.Dequeue(); //Point
                seconds = ExtractNumber(recognizedWords);

                if (recognizedWords.Peek() == "...") { recognizedWords.Dequeue(); } //Possible wildcard

                directionString = recognizedWords.Dequeue();

                //Generate an event to indicate the results of the recognition
                Subject subject = (Subject)Enum.Parse(typeof(Subject), subjectString, true);
                Coordinate coordinate;
                switch (directionString)
                {
                    case "north":
                        coordinate = new Latitude(degrees, minutes, seconds);
                        break;
                    case "west":
                        coordinate = new Longitude(degrees, minutes, seconds);
                        break;
                    default:
                        coordinate = null;
                        break;
                }
                if (RecognizedCoordinate != null)
                {
                    RecognizedCoordinate(subject, coordinate);
                }
            }
        }

        /// <summary>
        /// Extracts the sum of a set of numbers from the front of a queue, removing each
        /// as it is processed.
        /// </summary>
        /// <param name="words">the queue of words to extract the numbers from</param>
        /// <returns>the sum of the numbers extracted</returns>
        private int ExtractNumber(Queue<String> words)
        {
            int sum = 0;
            while (words.Count > 0 && numberStringToInt.ContainsKey(words.Peek()))
            {
                sum += numberStringToInt[words.Dequeue()];
            }
            return sum;
        }

        void recognizer_SpeechHypothesized(object sender, SpeechHypothesizedEventArgs e)
        {
            System.Console.WriteLine("\t(" + e.Result.Text + ")");
        }

        #region IDisposable Members

        public void Dispose()
        {
            recognizer.Enabled = false;
            recognizer.Dispose();
        }

        #endregion
    }
}
